#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <mysql/mysql.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <pthread.h>
#include "wrap.h"

#define SERV_PORT 8866
#define MAXLINE 80

MYSQL mysql;

pthread_t ntid;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

typedef struct User                 //用户信息
{
    char* id;
    char* name;
    char* pswd;
    struct User* next;   
}User;

typedef struct MsgInfo             //消息信息
{
    int cfd;
    char buf[MAXLINE];
    struct MsgInfo *next;
}MsgInfo;

typedef struct map                //id与文件描述符
{
    char * id;
    int cfd;

    struct map* next;
}Map;

MsgInfo* head = NULL;
User* onlinehead = NULL;
User* userhead = NULL;
Map* idmaphead = NULL;

void send_msg(int cfd, char* msg)
{
    write(cfd, msg, sizeof(msg));
    printf("server send :  ");
    write(1, msg, sizeof(msg));
}

//用户数据链表清空
void clear_userinfo()
{
    User* node = NULL;
    while (userhead != NULL)
    {
        node = userhead;
        userhead = userhead->next;
        free(node);
    }
}

//遍历数据库将数据添加到链表
void travel_mysql()
{
    clear_userinfo();

    MYSQL_ROW row;
    MYSQL_RES *res;
    char* query;
    int t,r;

    query = "select * from user_info";
    t = mysql_real_query(&mysql, query, (unsigned int)strlen(query));
    if (t)
    {
        printf("执行显示时出现异常：%s",mysql_error(&mysql));
    }

    res = mysql_store_result(&mysql);

    while(row = mysql_fetch_row(res))
    {
        User* user = malloc(sizeof(User));

        user->id = row[0];
        user->name = row[1];
        user->pswd = row[2];

        user->next = userhead;
        userhead = user;
    }

    User* node = userhead;
    while (node != NULL)
    {
        printf("%s---%s---%s\n", node->id, node->name, node->pswd);
        node = node->next; 
    }
    mysql_free_result(res);
}

//连接数据库
void connect_mysql()
{

    mysql_init(&mysql);   
    if(!mysql_real_connect(&mysql, "localhost", "root", "123456","chatdb",0,NULL,0))
    {
        printf("Connection failed,%s\n",mysql_error(&mysql));
    }
    else
    {
        mysql_set_character_set(&mysql, "utf8");
        printf("connect mysql success\n");

    }
    travel_mysql();
}

//字符串切割
int split(char dst[10][100], char* src, const char* spl)
{
    int n = 0;
    char* result = NULL;
    result = strtok(src, spl);
    while (result != NULL)
    {
        strcpy(dst[n++], result);
        result = strtok(NULL, spl);
    }

    return n;
}

//注册账号密码
void user_register(MsgInfo* info)
{
    int i;
    char* msg;
    char dst[10][100]; 
    int cnt = split(dst, info->buf, "|");
    for (i = 0; i < cnt; i++)
    {
        printf("%s   ", dst[i]);
    }
    printf("\n");

    User* node = userhead;
    while (node != NULL)
    {
        if (strcmp(node->id, dst[1]) == 0)
        {
            msg = "R#?";
            send_msg(info->cfd, msg);
            //write(info->cfd, msg, sizeof(msg));
            return;
        }
        node = node->next;
    }

    char* query;
    sprintf(query, "insert into user_info values('%s', '%s', '%s')", dst[1], dst[2], dst[3]);
    if (mysql_query(&mysql, query) != 0)
    {
        printf("insert err！\n");
        exit(1);
    }

    msg = "R#!";
    send_msg(info->cfd, msg);
    //write(info->cfd, msg, sizeof(msg));
    travel_mysql();
}

void get_online_user();
//用户登录，判断账号密码
void user_login(MsgInfo* info)
{
    int i;
    char msg[20];
    bzero(&msg, sizeof(msg));
    char dst[10][100]; 
    int cnt = split(dst, info->buf, "|");

    User* online = onlinehead;
    while (online != NULL)
    {      
        if (strcmp(online->id, dst[1]) == 0)
        {
            strcpy(msg, "L#?|1");  //已在线
            write(info->cfd, msg, sizeof(msg));
            return;
        }
        online = online->next;
    }

    User* node = userhead;
    while (node != NULL)
    {
        if (strcmp(node->id, dst[1]) == 0)
        {
            if (strcmp(node->pswd, dst[2]) == 0)
            {
                sprintf(msg, "L#!|%s|%s|%s", node->id, node->name, node->pswd);
                write(info->cfd, msg, sizeof(msg));
                User* user = malloc(sizeof(User));
                *user = *node;
                user->next = onlinehead;
                onlinehead = user;
                
                Map* map = malloc(sizeof(Map));
                map->id = node->id;
                map->cfd = info->cfd;
                map->next = idmaphead;
                idmaphead = map;

                get_online_user();
                return;
            }
        }
        node =  node->next;
    }
    strcpy(msg, "L#?|2");  //没有注册
    write(info->cfd, msg, sizeof(msg));
}

//用户退出
void user_logout(MsgInfo* info)
{
    int i;
    char msg[20];
    bzero(&msg, sizeof(msg));
    char dst[10][100]; 
    int cnt = split(dst, info->buf, "|");

    User* quit = onlinehead;
    User* ptr;
    while((quit != NULL) && (strcmp(quit->id, dst[1]) != 0))
    {
        ptr = quit;
        quit = quit->next;
    }
    if (strcmp(quit->id, dst[1]) == 0)
    {
        if (quit == onlinehead)
        {
            onlinehead = quit->next;
        }else
        {
            ptr->next = quit->next;
        }
        printf("%s --- %s ----- %s\n", quit->id, quit->name, quit->pswd);
        free(quit);

        strcpy(msg, "C#!");  
        write(info->cfd, msg, sizeof(msg));
    }else
    {
        strcpy(msg, "C#?");  
        write(info->cfd, msg, sizeof(msg));
    }

    Map* idmap = idmaphead;
    Map* iptr;
    while ((idmap != NULL) && (strcmp(idmap->id, dst[1]) != 0))
    {
        iptr = idmap;
        idmap = idmap->next;
    }
    if (strcmp(idmap->id, dst[1]) == 0)
    {
        if (idmap == idmaphead)
        {
            idmaphead = idmap->next;
        }else
        {
            iptr->next = idmap->next;
        }
        free(idmap);
    }

    get_online_user();
}
//获取在线用户
void get_online_user()
{
    char msg[200];
    char tmsg[100]; 
    bzero(&msg, sizeof(msg));
    bzero(&tmsg, sizeof(tmsg));
    
    User* online = onlinehead;
    sprintf(msg, "O#");
    while(online != NULL)
    {
        sprintf(tmsg, "%s|%s|%s|", online->id, online->name, online->pswd);
        strcat(msg, tmsg);
        online = online->next;
    }

    Map* idmap = idmaphead;
    while (idmap != NULL)
    {
        write(idmap->cfd, msg, sizeof(msg));
        idmap = idmap->next;
    }
}

//给全部在线用户发消息
void send_msg_all(MsgInfo* info)
{
    char msg[200];
    bzero(&msg, sizeof(msg));
    char dst[10][100]; 
    int cnt = split(dst, info->buf, "|");
    
    sprintf(msg, "A#!|%s", dst[2]);
    Map* idmap = idmaphead;
    while (idmap != NULL)
    {
        if (strcmp(idmap->id, dst[1]) != 0)
        {
            write(idmap->cfd, msg, sizeof(msg));
            write(1, msg, sizeof(msg));
        }
        idmap = idmap->next;
    }
}

//私聊
void send_msg_private(MsgInfo* info)
{
    char msg[200];
    bzero(&msg, sizeof(msg));
    char dst[10][100]; 
    int cnt = split(dst, info->buf, "|");
    
    sprintf(msg, "P#!|%s|%s", dst[1], dst[3]);
    Map* idmap = idmaphead;
    while (idmap != NULL)
    {
        if (strcmp(idmap->id, dst[2]) == 0)
        {
            write(idmap->cfd, msg, sizeof(msg));
            write(1, msg, sizeof(msg));
        }
        idmap = idmap->next;
    }
}

//子线程，处理信息
void *deal_message_thread(void *arg)
{
    MsgInfo* info = NULL;
    while(1)
    {
        pthread_mutex_lock(&mutex);
        if (head == NULL)
        {
            pthread_cond_wait(&cond, &mutex);
        }
        info = head;
        head = head->next;
        pthread_mutex_unlock(&mutex);

        switch(info->buf[0]){
            case 'A': send_msg_all(info); break;
            case 'P': send_msg_private(info); break;
        }
        free(info);
        usleep(1000);
    }
}


int main()
{
    connect_mysql();    //链接数据库

    //创建子线程
    int err;
    err = pthread_create(&ntid, NULL, deal_message_thread, "hello world");
    if (err != 0)
    {
        fprintf(stderr, "%s\n", strerror(err));
    }

    char str[INET_ADDRSTRLEN];
    int i, n;
    int cfd;
    int lfd = Socket(AF_INET, SOCK_STREAM, 0);//创建监听套接字

    struct sockaddr_in servaddr;
    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(SERV_PORT);

    //设置端口复用
    int flag = 1;
    setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag));
    //绑定
    Bind(lfd, (struct sockaddr*)&servaddr, sizeof(servaddr));
    Listen(lfd, 20);

    struct sockaddr_in cliaddr;
    socklen_t cliaddr_len = sizeof(cliaddr);

    //最大文件描述符
    int maxfd = lfd;
    //文件描述符读集合
    fd_set reads, temp;

    FD_ZERO(&reads);
    FD_SET(lfd, &reads);

    while(1)
    {
        temp = reads;
        int ret = select(maxfd+1, &temp, NULL, NULL, NULL);
        if (ret == -1)
        {
            perror("select error\n");
        }

        //客户端发起了新连接
        if (FD_ISSET(lfd, &temp))
        {
            //接受连接请求  -accept不阻塞
            cfd = Accept(lfd, (struct sockaddr*)&cliaddr, &cliaddr_len);
            printf("received from %s at PORT %d\n",
                    inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
                    ntohs(cliaddr.sin_port));

            //将cfd加入到待检测的读集合中
            FD_SET(cfd, &reads);
            //更新最大的文件描述符
            maxfd = maxfd < cfd ? cfd : maxfd;
        }
        for (i = lfd + 1; i <= maxfd; i++)
        {
            if (FD_ISSET(i, &temp))
            {
                char buf[MAXLINE];
                bzero(buf, MAXLINE);
                n = Read(i, buf, MAXLINE);
                if (n == -1)
                {
                    perror("recv error");
                }else if(n == 0)
                {
                    printf("客户端已经断开连接\n");
                    close(i);
                    //从读集合删除
                    FD_CLR(i, &reads);
                }else
                {   
                    MsgInfo* msginfo = malloc(sizeof(MsgInfo));
                    msginfo->cfd = i;
                    memcpy(msginfo->buf, buf, sizeof(buf));
                    
                    write(1, buf, sizeof(buf));
                    printf("\n");
                    if (msginfo->buf[0] == 'R')
                    {
                        user_register(msginfo);
                    }else if (msginfo->buf[0] == 'L')
                    {
                        user_login(msginfo); 
                    }else if (msginfo->buf[0] == 'C')
                    {
                        user_logout(msginfo);
                    }else
                    {
                        pthread_mutex_lock(&mutex);
                        msginfo->next = head;
                        head = msginfo;
                        pthread_mutex_unlock(&mutex);
                        pthread_cond_signal(&cond);
                    }   
                }
            }      
        }
        usleep(1000);
    }
    close(lfd);
    pthread_join(ntid, NULL);
    mysql_close(&mysql);
    return 0;
}
